﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Xml.Serialization;

namespace d39
{
    [Serializable]
    public struct LayerSet : IEnumerable<int>
    {
        private readonly int flags;

        public static LayerSet Empty { get { return default(LayerSet); } }

        // Note that this means that new LayerSet(i) when i is an integer has a different meaning inside this class from what it has outside it.
        // inside this class it calls this constructor and i means "flags"; outside this class it calls the params int[] version and i is a single layer.
        private LayerSet(int flags)
        {
            this.flags = flags;
        }

        public LayerSet(IEnumerable<int> layers)
        {
            var flags = 0;
            foreach (var i in layers)
            {
                flags |= getFlag(i);
            }
            this.flags = flags;
        }

        public LayerSet(LayerSet layers)
        {
            this.flags = layers.flags;
        }

        public LayerSet(params int[] layers)
            : this((IEnumerable<int>)layers) { }

        private static void checkRange(int i)
        {
            if (i < 1 || i > 31) throw new ArgumentOutOfRangeException("Attempt to use layer outside the legal range (1-31): " + i);
        }

        private static int getFlag(int i)
        {
            checkRange(i);
            return (1 << (i - 1));
        }

        public bool Contains(int i)
        {
            return (flags & getFlag(i)) != 0;
        }

        public bool IsEmpty { get { return flags == 0; } }

        // Returns 0 if this set does not contain any layers.
        // Normally I would want to return int? here to force code to handle that,
        // but in practice usage of this struct will always be forcing at least one layer
        // to be set.
        public int Highest
        {
            get
            {
                var _this = this;
                return (from i in Enumerable.Range(1, 30).Reverse()
                        where _this.Contains(i)
                        select i).FirstOrDefault();
            }
        }

        public IEnumerator<int> GetEnumerator()
        {
            var _this = this;
            return (from i in Enumerable.Range(1, 30)
                   where _this.Contains(i)
                   select i).GetEnumerator();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public static LayerSet operator +(LayerSet layers, int i)
        {
            return new LayerSet(layers.flags | getFlag(i));
        }

        public static LayerSet operator -(LayerSet layers, int i)
        {
            return new LayerSet(layers.flags & ~getFlag(i));
        }
        public static LayerSet operator &(LayerSet a, LayerSet b)
        {
            return new LayerSet(a.flags & b.flags);
        }
        public static LayerSet operator |(LayerSet a, LayerSet b)
        {
            return new LayerSet(a.flags | b.flags);
        }
        public static bool operator ==(LayerSet a, LayerSet b)
        {
            return a.flags == b.flags;
        }
        public static bool operator !=(LayerSet a, LayerSet b)
        {
            return a.flags != b.flags;
        }
        public override bool Equals(object obj)
        {
            return obj is LayerSet && ((LayerSet)obj).flags == flags;
        }
        public override int GetHashCode()
        {
            return flags.GetHashCode();
        }

        public override string ToString()
        {
            return Base26.IntToString(flags);
        }
        public static LayerSet ParseLayerSet(string str, int start, out int end)
        {
            return new LayerSet(Base26.ParseInt(str, start, out end));
        }

        public string ToXmlString()
        {
            return string.Join(" ", this);
        }
        public static LayerSet FromXmlString(string str)
        {
            return new LayerSet(from layer in str.Split(' ') select int.Parse(layer));
        }
    }
}